package com.supplychain.scm.core.task.util;

import cn.hutool.core.io.FileUtil;
import cn.hutool.http.HttpDownloader;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.supplychain.scm.core.basis.constant.StringPool;
import com.supplychain.scm.core.basis.enums.ProductInfoEnum;
import com.supplychain.scm.core.task.bean.TaskAsyncResultTO;
import com.supplychain.scm.core.task.constants.ExcelConstants;
import com.supplychain.scm.core.basis.constant.CommonConstants;
import com.supplychain.scm.core.common.util.SpringContextHolder;
import com.supplychain.scm.core.oss.AliOssService;
import com.supplychain.scm.core.oss.model.DbyFile;
import com.supplychain.scm.core.oss.model.UpFile;
import org.apache.commons.compress.utils.Lists;
import org.apache.commons.fileupload.disk.DiskFileItem;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.commons.fileupload.FileItem;
import org.springframework.util.Assert;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;

import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;

/**
 * @Author like
 */
public class EasyExcelUtil {
    private static final Logger log = LoggerFactory.getLogger(EasyExcelUtil.class);

    public static <T> AnalysisEventListener<T> getListener(Consumer<List<T>> consumer, int threshold) {
        return new AnalysisEventListener<T>() {
            /**
             * 存储数据集合
             */
            private List<T> list = new LinkedList<T>();

            /**
             * 这个每一条数据解析都会调用一次
             * @param t
             * @param analysisContext
             */
            @Override
            public void invoke(T t, AnalysisContext analysisContext) {
                // 如果一行Excel数据均为空值，则不装载该行数据
                if (isLineNullValue(t)) {
                    return;
                }
                list.add(t);
                if (list.size() == threshold) {
                    consumer.accept(list);
                    list.clear();
                }
            }

            /**
             * 所有数据解析完成 自动调用此方法
             * @param analysisContext
             */
            @Override
            public void doAfterAllAnalysed(AnalysisContext analysisContext) {
                if (list.size() > 0) {
                    consumer.accept(list);
                }
            }
        };
    }

	public static <T> AnalysisEventListener<T> getMaxCount(Consumer<Integer> consumer, int limitCount) {
		return new AnalysisEventListener<T>() {
			/** 行数 */
			private Integer count = 0;

			/**
			 * 这个每一条数据解析都会调用一次
			 * @param t
			 * @param analysisContext
			 */
			@Override
			public void invoke(T t, AnalysisContext analysisContext) {
				// 如果一行Excel数据均为空值，则不装载该行数据
				if (isLineNullValue(t)) {
					return;
				}
				count++;
				Assert.isTrue(limitCount >= count , "超出总行数限制，限制行数为：" + limitCount);
				consumer.accept(count);
			}

			/**
			 * 所有数据解析完成 自动调用此方法
			 * @param analysisContext
			 */
			@Override
			public void doAfterAllAnalysed(AnalysisContext analysisContext) {
				Assert.isTrue(!CommonConstants.ZERO.equals(count) , "文件解析记录数为0,可能是模板格式错误");
			}
		};
	}

    /**
     * 判断整行单元格数据是否均为空
     */
    private static <T> boolean isLineNullValue(T data) {
        if (data instanceof String) {
            return Objects.isNull(data);
        }
        try {
            List<Field> fields = Arrays.stream(data.getClass().getDeclaredFields())
                    .filter(f -> f.isAnnotationPresent(ExcelProperty.class))
                    .collect(Collectors.toList());
            List<Boolean> lineNullList = new ArrayList<>(fields.size());
            for (Field field : fields) {
                field.setAccessible(true);
                Object value = field.get(data);
                if (Objects.isNull(value)) {
                    lineNullList.add(Boolean.TRUE);
                } else {
                    lineNullList.add(Boolean.FALSE);
                }
            }
            return lineNullList.stream().allMatch(Boolean.TRUE::equals);
        } catch (Exception e) {
            log.error("读取数据行[{}]解析失败: {}", data, e.getMessage());
        }
        return true;
    }

	/**
	 * check excel上传数量
	 * @param stream
	 * @param maxCount 最大限制数量
	 * @param <T>
	 */
	public static <T> Integer checkMaxCount(InputStream stream, Class<T> tClass, int maxCount) {
		List<Integer> count = Arrays.asList(0);
		EasyExcelFactory.read(stream, tClass, getMaxCount(importCount -> {
			count.set(0, importCount);
		}, maxCount)).sheet().headRowNumber(1).doRead();
		return count.stream().findFirst().orElse(null);
	}

	/**
	 * 判断文件大小
	 * @param len 文件长度
	 * @param size 限制大小
	 * @param unit 限制单位（B,K,M,G）
	 * @return
	 */
	public static boolean checkFileSize(Long len, int size, String unit) {
		double fileSize = 0;
		if ("B".equals(unit.toUpperCase())) {
			fileSize = (double) len;
		} else if ("K".equals(unit.toUpperCase())) {
			fileSize = (double) len / 1024;
		} else if ("M".equals(unit.toUpperCase())) {
			fileSize = (double) len / 1048576;
		} else if ("G".equals(unit.toUpperCase())) {
			fileSize = (double) len / 1073741824;
		}
		if (fileSize > size) {
			return false;
		}
		return true;
	}

	public static <Model> TaskAsyncResultTO splitTaskHandler(String fileUrl, String sheetName, ProductInfoEnum productInfoEnum, Class<Model> modelClass) {
		return splitTaskHandler(fileUrl, sheetName, productInfoEnum, null, modelClass);
	}

	public static <Model> TaskAsyncResultTO splitTaskHandler(String fileUrl, String sheetName, ProductInfoEnum productInfoEnum, Integer splitCount, Class<Model> modelClass) {
		List<TaskAsyncResultTO.SubTask> subTasks = Lists.newArrayList();
		AnalysisEventListener<Model> listener = EasyExcelUtil.getListener(list -> {
			String fileName = ExcelConstants.DOWNLOAD_LOCAL_DEFAULT_DIR.concat(sheetName).concat(StringPool.UNDERSCORE).concat(UUID.randomUUID().toString())
					.concat(ExcelConstants.DOT_FILE_TYPE_EXCEL);
			ExcelWriter excelWriter = EasyExcel.write(fileName, modelClass).build();
			WriteSheet writeSheet = EasyExcel.writerSheet(sheetName).build();
			excelWriter.write(list, writeSheet);
			excelWriter.finish();

			// 上传文件
			DbyFile dbyFile = null;
			try {
				dbyFile = putFile(fileName, productInfoEnum);
			} catch (Exception e) {
				log.warn("splitTaskHandler策略处理器发生异常:{}", ExceptionUtils.getStackTrace(e));
			}
			subTasks.add(TaskAsyncResultTO.SubTask.builder().fileName(dbyFile.getName()).importFileUrl(dbyFile.getLink()).importCount(list.size()).build());
		}, Objects.nonNull(splitCount) ? splitCount : ExcelConstants.FIVE_HUNDRED_COUNT);
		EasyExcelFactory.read(getInputStream(fileUrl), modelClass, listener).sheet().headRowNumber(1).doRead();
		return TaskAsyncResultTO.builder().taskCount(subTasks.size()).subTasks(subTasks).build();
	}

	public static InputStream getInputStream(String fileUrl) {
		byte[] downloadBytes = HttpDownloader.downloadBytes(fileUrl);
		InputStream inputStream = new ByteArrayInputStream(downloadBytes);
		return inputStream;
	}

	public static DbyFile putFile(String fileName, ProductInfoEnum productInfoEnum) throws IOException {
		File file = FileUtil.file(fileName);
		MultipartFile multipartFile = file2MultipartFile(file);
		DbyFile dbyFile;
		try {
			AliOssService aliOssService = SpringContextHolder.getBean(AliOssService.class);
			Assert.notNull(aliOssService,"AliOssService uninitialized");
			UpFile upFile = new UpFile();
			upFile.setFile(multipartFile);
			upFile.setProductInfo(productInfoEnum);
			dbyFile = aliOssService.putFile(upFile);
		} finally {
			//删除本地文件
			if (file.exists()) {
				FileUtil.del(file);
			}
		}
		return dbyFile;
	}

	public static DbyFile putFile(File file, ProductInfoEnum productInfoEnum) throws IOException {
		MultipartFile multipartFile = file2MultipartFile(file);
		DbyFile dbyFile;
		try {
			AliOssService aliOssService = SpringContextHolder.getBean(AliOssService.class);
			Assert.notNull(aliOssService,"AliOssService uninitialized");
			UpFile upFile = new UpFile();
			upFile.setFile(multipartFile);
			upFile.setProductInfo(productInfoEnum);
			dbyFile = aliOssService.putFile(upFile);
		} finally {
			//删除本地文件
			if (file.exists()) {
				FileUtil.del(file);
			}
		}
		return dbyFile;
	}

	public static MultipartFile file2MultipartFile(File file) throws IOException {
		FileItem fileItem = new DiskFileItem(file.getName()
				, Files.probeContentType(file.toPath())
				, false, file.getName()
				, (int) file.length()
				, file.getParentFile());
		return new CommonsMultipartFile(fileItem);
	}
}
